﻿using CryForLua_wpf_AvEd.CodeCompletion;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Document;
using System;

namespace CryForLua_wpf_AvEd.CodeAnalysing
{
    public class LuaCodeRegion
    {
        public LuaCodeRegion() : this(-1, 0) 
        {
           
        }

        /// <summary>
        /// Specifies a new region in code.
        /// </summary>
        /// <param name="start">Where does the region start?</param>
        /// <param name="seondValue">Length or end.</param>
        /// <param name="bIsLength">Tells the constructor whether secondValue should be treated as length or end position.</param>
        public LuaCodeRegion(int start, int seondValue, bool bIsLength = false)
        {
            m_startAt = start;

            if (bIsLength)
            {
                m_length = seondValue;
                m_endAt = start + seondValue;
            }
            else
            {
                m_endAt = seondValue;
                m_length = m_endAt - m_startAt;
            }
        }

        public LuaCodeRegion(LuaCodeRegion luaBlock)
        {
           
            m_endAt = luaBlock.m_endAt;
            m_length = luaBlock.m_length;
            m_startAt = luaBlock.m_startAt;
        }

        public LuaCodeRegion Copy()
        {
            return new LuaCodeRegion(this);
        }

        public bool Contains(int pos)
        {
            if (pos >= m_startAt && pos <= m_endAt)
                return true;
            else
                return false;
        }

        public static bool Contains(int pos, ref List<LuaCodeRegion> list)
        {
            for (int i = 0; i < list.Count; ++i)
            {
                if (list[i].Contains(pos))
                    return true;
            }

            return false;
        }

        public int m_startAt;
        public int m_length;
        public int m_endAt;
    };

    public class LuaCodeBlock : LuaCodeRegion
    {
        public enum BlockType
        {
            eBT_Function,
            eBT_IfCondition,
            eBT_DoBlock,
            eBT_Unknown
        };
        public LuaCodeBlock() : this(BlockType.eBT_Unknown, -1, 0) 
        {
           
        }

       /// <summary>
       /// <see cref="LuaCodeRegion"/>
       /// </summary>
       /// <param name="start"></param>
       /// <param name="seondValue"></param>
       /// <param name="bIsLength"></param>
        public LuaCodeBlock(BlockType type, int start, int seondValue, bool bIsLength = false)
        {
            m_startAt = start;

            if (bIsLength)
            {
                m_length = seondValue;
                m_endAt = start + seondValue;
            }
            else
            {
                m_endAt = seondValue;
                m_length = m_endAt - m_startAt;
            }

            m_nType = type;
        }

        public LuaCodeBlock(LuaCodeBlock luaBlock)
        {
           
            m_endAt = luaBlock.m_endAt;
            m_length = luaBlock.m_length;
            m_startAt = luaBlock.m_startAt;
            m_nType = luaBlock.m_nType;
        }

        public BlockType m_nType;
    };

    public class LuaTable
    {
        public LuaTable() : this("<unnamed table>", new List<LuaKeyValuePair>())
        {
        }

        public LuaTable(string sName) : this(sName, new List<LuaKeyValuePair>())
        {
        }

        public LuaTable(string sName, List<LuaKeyValuePair> values)
        {
            m_sName = sName;
            m_values = values;
            m_codeBlock = new LuaCodeRegion();
        }

        public LuaTable(LuaTable tbl)
            : this(tbl.m_sName, tbl.m_values)
        {
            m_codeBlock = new LuaCodeRegion(tbl.m_codeBlock);
        }

        public override string ToString()
        {
            string result = m_sName + " = \n{ \n";

            foreach (LuaKeyValuePair pair in m_values)
            {
                result += "     " + pair.ToString() + "\n";
            }

            result += "\n }";


            return result;
        }
        public string m_sName;
        public List<LuaKeyValuePair> m_values;
        public LuaCodeRegion m_codeBlock;
    };

    public class LuaKeyValuePair
    {
        public LuaKeyValuePair(string sKey, string sValue, string sDescription = "")
        {
            m_sKey = sKey;
            m_sValue = sValue;
            m_sDescritpion = sDescription;
        }

        public LuaKeyValuePair(string sKey, LuaTable table, string sDescription = "")
        {
            m_sKey = sKey;
            m_tableValue = new LuaTable(table);
            m_sDescritpion = sDescription;
        }

        public override string ToString()
        {
            string res = "";

            if (m_sValue != null)
            {
                res += m_sKey + " = " + m_sValue;
            }
            else
            {
                res = m_tableValue.ToString();
            }
            res += " --Descritpion: " + m_sDescritpion;

            return res;
        }

        public string m_sKey;
        public string m_sValue;
        public string m_sDescritpion;
        public LuaTable m_tableValue;
    };


    public struct LuaDocument
    {
        public List<CodeTable.LuaFunction> functions;
        public List<LuaTable> tables;
        public List<LuaCodeRegion> logicalRegions;
    };

    /// <summary>
    /// Provides functions to analyse a code snipped.
    /// </summary>
    public class CodeAnalyser
    {

        public static string s_codeText = "";
        public static LuaDocument m_doc = new LuaDocument();

        /// <summary>
        /// Analyses a given string and builds a LuaFunction out of it.
        /// </summary>
        /// <param name="sFunctionHead">The complete function head (e.g. "function doStuff(tableParam, boolParam)"</param>
        /// <param name="outFunction">This is the function that gets filled</param>
        /// <param name="bHasDescription">Reserved for later use</param>
        /// <returns>True on succes</returns>
        public  bool ParseRegularFunctionDefinition(string sFunctionHead, ref CodeTable.LuaFunction outFunction, out string sFunctionIsInThisTable, bool bHasDescription = false)
        {

            sFunctionIsInThisTable = "None";
            return false;
        }


        public static void ParseScriptStatic()
        {
            m_doc = ParseScript(s_codeText);
        }
        public static LuaDocument ParseScript(string sScriptText)
        {
            LuaDocument doc = new LuaDocument();

            doc.tables = new List<LuaTable>();
            doc.logicalRegions = new List<LuaCodeRegion>();
            doc.functions = new List<CodeTable.LuaFunction>();

            doc.logicalRegions = FindTablesInCode(sScriptText);


            // Add all tables
            for (int i = 0; i < doc.logicalRegions.Count; ++i)
            {
                LuaTable table = new LuaTable();

                ParseTable(sScriptText, doc.logicalRegions[i], ref table);

                doc.tables.Add(table);
            }


            // Add functions

            List<LuaCodeBlock> blocks = FindBlocks(sScriptText);

            for (int i = 0; i < blocks.Count; ++i)
            {
                if (blocks[i].m_nType == LuaCodeBlock.BlockType.eBT_Function)
                {
                    CodeTable.LuaFunction function = new CodeTable.LuaFunction();
                    ParseFunction(sScriptText, blocks[i], ref function);

                    doc.functions.Add(function);
                }
            }


            return doc;
        }

        public static bool ParseTable(string sTableText, ref LuaTable outTable, bool bHasDescription = false)
        {
           
            int tableBodyStart = sTableText.IndexOf('{');

            List<LuaCodeRegion> lits = FindTablesInCode(sTableText);
            lits.RemoveAt(0);

            // If there's no curly bracket it's not a table
            if (tableBodyStart == -1)
            {
                return false;
            }

            int tableNameEnd = sTableText.LastIndexOf('=', tableBodyStart);


            // See if the table has a name 

            // TODO
            // Really check if the table even has a name 
            if (tableNameEnd != -1)
            {
                string sTableName = sTableText.Substring(0, tableNameEnd);
                sTableName = sTableName.Trim();
                outTable.m_sName = sTableName;
            }

            // Going for regular key - value pairs first
            Regex keyValueRegex = new Regex(@"\s*\w+\s*=\s*(""?-?\w+\.?\w*\s*[\*?\+?\-?\\?]?\s*\w*\.?\w*""?)|(\w+\s*\(\.*\)\s*)\s*");
            MatchCollection keyValueMatches = keyValueRegex.Matches(sTableText, tableBodyStart);

            // TODO:
            // Filter functions out, meaning somevla=function() end
            // Assignments inside these functions get interpreted as table values

            for (int i = 0; i < keyValueMatches.Count; ++i)
            {
                Match curMatch = keyValueMatches[i];

                if (LuaCodeRegion.Contains(curMatch.Index, ref lits))
                    continue;

                string matchedString = curMatch.Value;
                matchedString = matchedString.Trim();

                int keyEnd = matchedString.IndexOf('=');
                int valueStart = keyEnd + 1;

                if (keyEnd == -1)
                {
                    continue;
                }

                string sKey = matchedString.Substring(0, keyEnd);
                sKey = sKey.Trim();
                
                string sValue = matchedString.Substring(valueStart, matchedString.Length - valueStart);
                sValue = sValue.Trim();

                if (sValue == "function")
                {
                    int functionStart = sTableText.IndexOf("function", curMatch.Index);
                    int functionEnd = sTableText.IndexOf(")", functionStart);

                    if (functionEnd != -1 && functionStart != -1)
                        sValue = sTableText.Substring(functionStart, functionEnd - functionStart + 1).Trim();
                }


                int commentPos = sTableText.IndexOf("--", curMatch.Index);
                int newLinePos = sTableText.IndexOf("\r\n", curMatch.Index + curMatch.Length);

                string desc = "";

                if (commentPos != -1 && commentPos < newLinePos)
                {
                    desc = sTableText.Substring(commentPos + 2, newLinePos - commentPos - 2);
                }
             
                sValue = sValue.Trim();

                outTable.m_values.Add(new LuaKeyValuePair(sKey, sValue, desc));    
            }

           

            // Now check if the table contains a table

            int lastEnd = 0;

            for (int i = 0; i < lits.Count; ++i)
            {

               if (lastEnd > lits[i].m_startAt)
                    continue;
                LuaTable table = new LuaTable();
                bool succes = ParseTable(sTableText, lits[i], ref table);

                if (succes)
                {
                    outTable.m_values.Add(new LuaKeyValuePair(table.m_sName, table));
                }

                lastEnd = lits[i].m_endAt;
            }

            /*Regex tableRegex = new Regex(@"\s*\w+\s*=\s*{.*}\s*", );
            MatchCollection tableMatches = tableRegex.Matches(sTableText, tableBodyStart);


            for (int i = 0; i < tableMatches.Count; ++i)
            {
                Match match = tableMatches[i];

                string matchString = match.Value.Trim();

                LuaTable table = new LuaTable();
                bool succes = ParseTable(matchString, ref table);

                if (succes)
                {
                    outTable.m_values.Add(new LuaKeyValuePair(table.m_sName, table));
                }
            }*/

            return true;
        }

        /// <summary>
        /// Parses a table inside the given code.
        /// </summary>
        /// <param name="sCodeTest">The code text</param>
        /// <param name="tableRegion">The region in which the table resides inside sCodeText</param>
        /// <param name="outLuaTable">This will be filled with the parsed information</param>
        /// <param name="bHasDescription">reserved for later use</param>
        /// <returns></returns>
        public static bool ParseTable(string sCodeTest, LuaCodeRegion tableRegion, ref LuaTable outLuaTable, bool bHasDescription = false)
        {
            bool bSuccess = false;
            Regex nameRegex = new Regex(@"\s*\w+\s*=\s*{", RegexOptions.RightToLeft);

            Match firstMatch = nameRegex.Match(sCodeTest, tableRegion.m_startAt + 1);

            if (firstMatch != null && firstMatch.Success && (firstMatch.Index + firstMatch.Length) >= tableRegion.m_startAt)
            {
                if (firstMatch.Index + firstMatch.Length == tableRegion.m_startAt + 1)
                {
                    string name = firstMatch.Value;
                    name = name.Substring(0, name.Length - 1);
                    name = name.Trim();
                    name = name.Substring(0, name.Length - 1);
                    name = name.Trim();

                    outLuaTable.m_sName = name;

                    bSuccess = ParseTable(sCodeTest.Substring(firstMatch.Index, tableRegion.m_endAt - firstMatch.Index).TrimStart(), ref outLuaTable, bHasDescription);
                }
            }
            else
            {
                bSuccess = ParseTable(sCodeTest.Substring(tableRegion.m_startAt, tableRegion.m_length), ref outLuaTable, bHasDescription);
            }


            outLuaTable.m_codeBlock = tableRegion.Copy();




            return bSuccess;
        }

        public static bool ParseFunctionDescription(string sFunctionText, ref CodeTable.LuaFunction outFunction)
        {
            int descStart = sFunctionText.IndexOf("--#desc");
            if (descStart != -1)
            {
                int newLine = sFunctionText.IndexOf("\r\n", descStart);

                if (newLine == -1)
                    newLine = sFunctionText.Length - 1;
                descStart += "--#desc".Length;
                outFunction.description = sFunctionText.Substring(descStart, newLine - descStart).Trim();
            }

            int returnStart = sFunctionText.IndexOf("--#returns");

            if (returnStart != -1)
            {
                int newLine = sFunctionText.IndexOf("\r\n", returnStart);

                if (newLine == -1)
                    newLine = sFunctionText.Length - 1;

                returnStart += "--#returns".Length;
                outFunction.returnValue = sFunctionText.Substring(returnStart, newLine - returnStart).Trim();
            }

            // Params need other descritpion method...
            int paramStart = 0;
            if (outFunction.paramList == null)
                outFunction.paramList = new List<CodeTable.LuaParameter>();

            while (true)
            {
                paramStart = sFunctionText.IndexOf("--#p", paramStart + 1);

                if (paramStart == -1)
                    break;

                int newLine = sFunctionText.IndexOf("\r\n", paramStart);

                paramStart += "--#p".Length;
                string wholeThing = sFunctionText.Substring(paramStart, newLine - paramStart).Trim();

                int firstSpace = wholeThing.IndexOf(' ');

                if (firstSpace == -1)
                    firstSpace = wholeThing.Length;

                string paramName = wholeThing.Substring(0, firstSpace);
                string paramDesc = wholeThing.Substring(firstSpace, wholeThing.Length - firstSpace).Trim();

                outFunction.paramList.Add(new CodeTable.LuaParameter(paramName, paramDesc));
               
            }



            return true;
        }

        public static bool ParseFunction(string sFunctionText, ref CodeTable.LuaFunction outFunction)
        {
            
            int functionStart = sFunctionText.IndexOf("function");
    
            
            if (functionStart != -1 && outFunction != null)
            {

                // Function description
                ParseFunctionDescription(sFunctionText, ref outFunction);

                // Function name

                int headStart = functionStart + "function".Length + 1;
                int headEnd = sFunctionText.IndexOf(')', Math.Min(headStart, sFunctionText.Length - 1));

                if (headEnd != -1)
                {
                    string sHead = sFunctionText.Substring(headStart, headEnd + 1 - headStart);
                    outFunction.name = sHead;
                }

                // formal parameters
                // 
                if (outFunction.paramList != null)
                {
                    int paramCount = 0;

                    for (int i = 0; i < outFunction.name.Length; ++i)
                        if (outFunction.name[i] == ',')
                            ++paramCount;

                    if (paramCount == 0)
                        return false;

                    int commaStart = 0;
                    int lastComma = 0;

                    CodeTable.LuaParameter param = null;
                        while (commaStart != -1)
                        {
                    
                            commaStart = outFunction.name.IndexOf(',', commaStart + 1);

                            if (commaStart == -1)
                            {
                                int functionEnd = outFunction.name.IndexOf(')');

                                if (functionEnd == -1)
                                    return false;

                                int paramStart = outFunction.name.LastIndexOf(',', functionEnd) + 1;

                                param = new CodeTable.LuaParameter(outFunction.name.Substring(paramStart, functionEnd - paramStart), "<Missing Description>");
                                break;
                            }

                            if (lastComma == 0)
                            {
                                int paramsStart = outFunction.name.IndexOf('(');

                                if (paramsStart == -1)
                                    return false;

                                ++paramsStart;
                                param = new CodeTable.LuaParameter(outFunction.name.Substring(paramsStart, commaStart - paramsStart), "<Missing Description>");

                                lastComma = commaStart + 1;

                            }
                            else
                            {
                                param = new CodeTable.LuaParameter(outFunction.name.Substring(lastComma, commaStart - lastComma), "<Missing Description>");
                                lastComma = commaStart + 1;
                            }

                            bool bExists = false;
                            foreach (CodeTable.LuaParameter par in outFunction.paramList)
                            {
                                if (par.name == param.name)
                                {
                                    bExists = true;
                                    break;
                                }
                                    
                            }

                            if (!bExists) 
                                outFunction.paramList.Add(param);

                        }


                    
                }
            }
            return false;
        }

        public static void ParseStatic()
        {
            m_doc = ParseScript(s_codeText);
        }
        public static bool ParseFunction(string sCodeText, LuaCodeBlock block, ref CodeTable.LuaFunction outFunction)
        {
            string functionText = sCodeText.Substring(block.m_startAt, block.m_length);

            int descPos = sCodeText.LastIndexOf("--#desc", block.m_startAt);

            outFunction.m_codeBlock = block;

            if (descPos == -1 || GetNextValidKeyword(block.m_startAt, "end", sCodeText) > descPos)
                return ParseFunction(functionText, ref outFunction);
            else
                return ParseFunction(sCodeText.Substring(descPos, block.m_endAt - descPos), ref outFunction);

            
        }

        public static List<LuaCodeRegion> FindTablesInCode(string sCodeText)
        {

            int openingBrackestsCount = 0;
            int closingBracketsCount = 0;

            for (int i = 0; i < sCodeText.Length; ++i)
            {
                char curChar = sCodeText[i];

                if (curChar == '{' && !IsCommentedOut(i, sCodeText))
                    ++openingBrackestsCount;
                else if (curChar == '}' && !IsCommentedOut(i, sCodeText))
                    ++closingBracketsCount;

            }

            List<LuaCodeRegion> regions = new List<LuaCodeRegion>();

            if (openingBrackestsCount != closingBracketsCount)
                return regions;


            // If there are no brackets at all do nothing
            if (sCodeText.IndexOf('{') == -1)
                return regions;

            int nClosingBracketPos = 0;

            int nOpeningBracketPos = 0;

            // Used for nestet stuff
            List<int> usedPositions = new List<int>();

            while (true)
            {
                nClosingBracketPos = sCodeText.IndexOf('}', nClosingBracketPos + 1);

                if (nClosingBracketPos == -1)
                    break;

                if (IsCommentedOut(nClosingBracketPos, sCodeText))
                    continue;

                // Search the first former { to the already found }
                nOpeningBracketPos = sCodeText.LastIndexOf('{', nClosingBracketPos);

                if (nOpeningBracketPos == -1)
                    return regions;




                while (usedPositions.Contains(nOpeningBracketPos) || IsCommentedOut(nOpeningBracketPos, sCodeText))
                {

                    // Position is already used meaning that the corresponding { is 
                    //even closer to the beginning of the text
                    nOpeningBracketPos = sCodeText.LastIndexOf('{', nOpeningBracketPos - 1);

                    if (nOpeningBracketPos == -1)
                        return null;
                }

                // A valid position has been found, add to the used 
                usedPositions.Add(nOpeningBracketPos);

                // +1 so the } is inlcuded in the folding
                regions.Add(new LuaCodeRegion(nOpeningBracketPos, nClosingBracketPos + 1));
            }

            regions.Sort((a, b) => (a.m_startAt.CompareTo(b.m_startAt)));
            return regions;
        }

        /// <summary>
        /// Checks if pos is commented out (line or block comment).
        /// Use this overload if you have acces to a TextArea containing the code.
        /// It's more reliable that the other overloads.
        /// </summary>
        /// <param name="pos">pos to check</param>
        /// <param name="area">TextArea containing the text.</param>
        /// <returns></returns>
        public static bool IsCommentedOut(int pos, TextArea area)
        {
            string sText = area.Document.GetText(0, area.Document.TextLength);
            TextDocument document = area.Document;
            bool bIsCommentedOut = false;

            // We'll start with single line comments as they are far more common than block comments

            DocumentLine line = document.GetLineByOffset(pos);

            string lineText = document.GetText(line);

            int commentPos = lineText.IndexOf("--");


            if (commentPos != -1)
            {
                commentPos += line.Offset;

                if (commentPos > pos)
                    bIsCommentedOut = false;
                else
                    bIsCommentedOut = true;

                // If it's commented out we can stop
                if (bIsCommentedOut)
                    return bIsCommentedOut;

            }
            // Up until here we only checked single line comments

            bIsCommentedOut = IsInsideBlockComment(pos, sText);
           

            return bIsCommentedOut;
        }

        /// <summary>
        /// Correct behaviour of this method is not guaranteed until further testing has been done.
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="sText"></param>
        /// <returns></returns>
        public static bool IsCommentedOut(int pos, string sText)
        {
            bool bIsCommentedOut = false;

            if (String.IsNullOrWhiteSpace(sText))
                return false;

            if (pos >= sText.Length)
            {
                string message = "IsCommentedOut: pos is greater than or equal to sText.Length (pos: " + pos.ToString() + " Length: " + sText.Length.ToString() + " )";
                throw new ArgumentOutOfRangeException("pos", message);
            }

            if (sText[pos] == '-')
            {
                if (pos - 1 != -1)
                {
                    // pos is - and pos -1 is - so it's the comment itself
                    if (sText[pos - 1] == '-')
                    {
                        return true;
                    }
                }

                if (pos + 1 < sText.Length)
                {
                    // pos is - and pos + 1 is - so it's the comment itself
                    if (sText[pos + 1] == '-')
                    {
                        return true;
                    }
                }
            }

           


            int commentPos = sText.LastIndexOf("--", pos);
            int lasNewLine = sText.LastIndexOf('\n', pos);

            if (lasNewLine == -1 && commentPos != -1)
                return true;

            if (lasNewLine < commentPos && commentPos != -1)
                return true;

            bIsCommentedOut = IsInsideBlockComment(pos, sText);

            return bIsCommentedOut;
        }

        public static bool IsInsideBlockComment(int pos, string sText)
        {
            bool bIsCommentedOut = false;

            int lastBlockCommentStart = sText.LastIndexOf("--[[", pos);
            int lastBlockCommetnEnd = sText.LastIndexOf("]]", pos);

            if (lastBlockCommentStart > lastBlockCommetnEnd)
                bIsCommentedOut = true;
            else
                bIsCommentedOut = false;  // also takes the absence of blockcomments into account

            return bIsCommentedOut;
        }


        public static List<LuaCodeBlock> FindBlocks(string sText)
        {
            List<LuaCodeBlock> blocks = new List<LuaCodeBlock>();

            if (sText.IndexOf("end") == -1)
                return blocks;

            int nClosingBracketPos = 0;

            int nFunctionPos = 0;
            int nIfPos = 0;
            int nDoPos = 0;
            int closestPos = 0;
            int finalPos = 0;


            // Used for nestet stuff
            List<int> usedPositions = new List<int>();

            while (true)
            {
                nClosingBracketPos = sText.IndexOf("end", nClosingBracketPos + 1);
                nClosingBracketPos = GetNextValidKeyword(nClosingBracketPos, "end", sText, false);

                if (nClosingBracketPos == -1)
                    break;

                if (IsCommentedOut(nClosingBracketPos, sText))
                    continue;

                closestPos = nClosingBracketPos;

                do
                {
                    nFunctionPos = sText.LastIndexOf("function", Math.Max(closestPos - 1, 0));
                    nFunctionPos = GetNextValidKeyword(nFunctionPos, "function", sText);

                    nIfPos = sText.LastIndexOf("if", Math.Max(closestPos - 1, 0));
                    nIfPos = GetNextValidKeyword(nIfPos, "if", sText);

                    nDoPos = sText.LastIndexOf("do", Math.Max(closestPos - 1, 0));
                    nDoPos = GetNextValidKeyword(nDoPos, "do", sText);

                    closestPos = Math.Max(Math.Max(nFunctionPos, nDoPos), nIfPos);

                    if (closestPos == -1)
                        break;

                    finalPos = closestPos;

                } while (IsCommentedOut(closestPos, sText) || usedPositions.Contains(finalPos));

                if (closestPos == -1)
                    break;

               
                LuaCodeBlock.BlockType blockType = LuaCodeBlock.BlockType.eBT_Unknown;

                if (closestPos == nFunctionPos)
                    blockType = LuaCodeBlock.BlockType.eBT_Function;
                else if (closestPos == nIfPos)
                    blockType = LuaCodeBlock.BlockType.eBT_IfCondition;
                else if (closestPos == nDoPos)
                    blockType = LuaCodeBlock.BlockType.eBT_DoBlock;

                if (finalPos > nClosingBracketPos)
                    finalPos = closestPos;

                usedPositions.Add(finalPos);



                blocks.Add(new LuaCodeBlock(blockType, finalPos, nClosingBracketPos + "end".Length));
            }

            return blocks;
        }

        public static int CheckIsKeyWord(int posToCheck, string sKeyWord, string sText)
        {
            if (posToCheck == -1)
                return -1;

            // Check before the supposed keyword
            if (posToCheck > 0)
            {
                // If its 0 there's no sense in checking before it
                char chBeforeKW = sText[posToCheck - 1];

                // If there's no whitespace before it, it's not a keyword
                if (!Char.IsWhiteSpace(chBeforeKW))
                {
                    // Except if its a 'if()then' construct
                    if (sKeyWord != "then")
                    {
                        return -1;
                    }
                    else
                    {
                        if (chBeforeKW != ')')
                        {
                            return -1;
                        }
                    }
                }
            }

            // Check behind the supposed Keyword

            int checkPos = posToCheck + sKeyWord.Length;

            // If the character after the keyword doesn't exist, assume it is a keyword
            if (checkPos >= sText.Length)
                return posToCheck;

            char chAfterKW = sText[checkPos];


            if (!Char.IsWhiteSpace(chAfterKW) && chAfterKW != ';')
            {
                if (sKeyWord == "if" || sKeyWord == "function")
                {
                    // These need special treating since function and if clauses can be followed directly by parenthesises

                    if (chAfterKW == '(')
                    {
                        // No whitespace but parenthesis == keyword
                        return posToCheck;
                    }
                    else
                    {
                        // No parenthesis and no whitespace == no keyword
                        return -1;
                    }
                }
                else if (sKeyWord == "end")
                {
                    if (chAfterKW == ',')
                        return posToCheck;
                    else
                        return -1;
                }


                // No special keyword and no whitespace == no keyword

                return -1;
            }


            // Whitespaces before and after the keyword == keyword

            return posToCheck;
        }

        public static int GetNextValidKeyword(int startingPos, string sKeyword, string sText, bool bSearchBackwards = true)
        {
            int lastCheckedPos = startingPos;
            // If there are no keywords at all, don't do anything
            if (startingPos != -1)
            {
                // Check if its a keyword
                while ((startingPos = CheckIsKeyWord(startingPos, sKeyword, sText)) == -1)
                {
                    // No, so search the next occurence
                    if (bSearchBackwards)
                        startingPos = sText.LastIndexOf(sKeyword, Math.Max(lastCheckedPos - 1, 0));
                    else
                        startingPos = sText.IndexOf(sKeyword, Math.Min(lastCheckedPos + 1, sText.Length));
                    // No more occurences? Break.
                    if (startingPos == -1)
                        return -1;

                    lastCheckedPos = startingPos;
                }
            }
            else
                return -1;

            return startingPos;
        }
      
    };
}